package renderer;

import java.awt.Color;
import java.awt.GraphicsConfigTemplate;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.util.ArrayList;
import java.util.Map;

import javax.media.j3d.AmbientLight;
import javax.media.j3d.Appearance;
import javax.media.j3d.BoundingSphere;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ColoringAttributes;
import javax.media.j3d.DirectionalLight;
import javax.media.j3d.GraphicsConfigTemplate3D;
import javax.media.j3d.Group;
import javax.media.j3d.LineArray;
import javax.media.j3d.LineAttributes;
import javax.media.j3d.LineStripArray;
import javax.media.j3d.Material;
import javax.media.j3d.Node;
import javax.media.j3d.PointArray;
import javax.media.j3d.PointAttributes;
import javax.media.j3d.PolygonAttributes;
import javax.media.j3d.Shape3D;
import javax.media.j3d.Transform3D;
import javax.media.j3d.TransformGroup;
import javax.media.j3d.View;
import javax.vecmath.AxisAngle4f;
import javax.vecmath.Color3f;
import javax.vecmath.Point3d;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import com.sun.j3d.utils.behaviors.vp.OrbitBehavior;
import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.geometry.Sphere;
import com.sun.j3d.utils.universe.SimpleUniverse;
import com.sun.j3d.utils.universe.ViewingPlatform;

/**
 * Rendering engine for the fiber sets
 *
 */
public class FluidRendererCore {

	private SimpleUniverse universe;
	private OrbitBehavior orbitBehavior;
	private BranchGroup root;
	private BranchGroup spheresBG;
	private Canvas3D canvas;

	private boolean first = true;


	public FluidRendererCore()
	{
		GraphicsConfigTemplate3D graphicsTemplate = new GraphicsConfigTemplate3D();
		graphicsTemplate.setDepthSize(24);
		graphicsTemplate.setSceneAntialiasing(GraphicsConfigTemplate.PREFERRED);
		GraphicsConfiguration conf = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getBestConfiguration(graphicsTemplate);
		canvas = new Canvas3D(conf);
		preSetupRender();
	}


	public Canvas3D getCanvas()
	{
		return canvas;
	}



	private void preSetupRender()
	{
		universe = new SimpleUniverse(canvas);
		orbitBehavior = new OrbitBehavior(canvas, OrbitBehavior.REVERSE_ALL | OrbitBehavior.PROPORTIONAL_ZOOM);

		root = new BranchGroup();
		root.setCapability(Node.ALLOW_BOUNDS_READ);
		root.setCapability(Group.ALLOW_CHILDREN_EXTEND);
		root.setCapability(Group.ALLOW_CHILDREN_WRITE);
		root.setCapability(Group.ALLOW_CHILDREN_READ);
		root.setCapability(BranchGroup.ALLOW_DETACH);
		
		spheresBG = new BranchGroup();
		spheresBG.setCapability(Node.ALLOW_BOUNDS_READ);
		spheresBG.setCapability(Group.ALLOW_CHILDREN_EXTEND);
		spheresBG.setCapability(Group.ALLOW_CHILDREN_WRITE);
		spheresBG.setCapability(Group.ALLOW_CHILDREN_READ);
		spheresBG.setCapability(BranchGroup.ALLOW_DETACH);
		
		root.addChild(spheresBG);
	}
	

	private void postSetupRender()
	{
//		root.addChild(createAxis((BoundingSphere)root.getBounds()));
		//		root.addChild(createAxis((BoundingSphere)root.getBounds()));
		
		universe.addBranchGraph(root);
		
		universe.getViewingPlatform().setNominalViewingTransform();

		BoundingSphere boundingSphere = (BoundingSphere) root.getBounds();
		float boundingRadius = (float) boundingSphere.getRadius(); 
		Point3d boundingCenter = new Point3d();
		boundingSphere.getCenter(boundingCenter);

		BoundingSphere bounds = new BoundingSphere(boundingCenter, boundingRadius * 20);
		orbitBehavior.setSchedulingBounds(bounds);

		setLighting(bounds);

		ViewingPlatform viewingPlatform = universe.getViewingPlatform();
		viewingPlatform.setViewPlatformBehavior(orbitBehavior);

		View view = universe.getCanvas().getView();
		view.setBackClipDistance(boundingRadius * 1.5);

		Point3d boundsCenter = new Point3d();
		bounds.getCenter(boundsCenter);


		TransformGroup vpTransformGroup = viewingPlatform.getViewPlatformTransform();
		Transform3D vpTransform3D = new Transform3D();	
		Vector3f transVector = new Vector3f();

		//Get the transformation vector from the transform3d of the transform group
		vpTransformGroup.getTransform(vpTransform3D);   
		vpTransform3D.get(transVector);

		transVector.x = (float) boundsCenter.x;
		transVector.y = (float) boundsCenter.y;
		transVector.z = (float) boundsCenter.z - (boundingRadius * 2.5f);
		vpTransform3D.setTranslation(transVector);
		AxisAngle4f cameraRotationTop = new AxisAngle4f(new Vector3f(0, 1, 0), (float)Math.toRadians(180));
		vpTransform3D.setRotation(cameraRotationTop);

		orbitBehavior.setRotationCenter(boundsCenter);
		orbitBehavior.setHomeTransform(vpTransform3D);
		double cameraDistance = boundsCenter.distance(new Point3d(new double[]{transVector.x, transVector.y, transVector.z}));
		orbitBehavior.setTransFactors(cameraDistance * 0.1, cameraDistance * 0.1);

		orbitBehavior.goHome();

	}
	public void renderFluid(double[][][] matrix)
	{
		spheresBG.detach();
		spheresBG.removeAllChildren();

		int counter = 0;
		for(int i = 0; i < matrix.length; i++)
		{
			for(int j = 0; j < matrix[0].length; j++)
			{
				for(int k = 0; k < matrix[0][0].length; k++)
				{
					double data = matrix[i][j][k];
					if(data != 0)
					{
						TransformGroup sphereTg = new TransformGroup();
						sphereTg.setCapability(Group.ALLOW_CHILDREN_READ);
						sphereTg.setCapability(Group.ALLOW_CHILDREN_EXTEND);
						sphereTg.setCapability(Group.ALLOW_CHILDREN_WRITE);
						Transform3D spheret3d = new Transform3D();
						spheret3d.setTranslation(new Vector3d(i * 2, j * 2 , k * 2));
						sphereTg.setTransform(spheret3d);
						
						Sphere sphere = new Sphere(1);
						sphere.setCapability(BranchGroup.ALLOW_DETACH);
						sphereTg.addChild(sphere);
						spheresBG.addChild(sphereTg);
						counter++;
						
						
//						spheresBG.addChild(createPoint(new double[]{i * 2, j * 2 , k * 2}, Color.GREEN));
//						counter++;
					}
				}	
			}
		}
		
		root.addChild(spheresBG);
		System.out.println("Spheres drawn: " + counter );
		if(first)
		{
			postSetupRender();
			first = false;
		}
	}

	private void setLighting(BoundingSphere bounds)
	{
		BranchGroup lightGroup = new BranchGroup();
		

		Color3f light1Color = new Color3f(1f, 1f, 1f);
		Vector3f light1Direction = new Vector3f(1.0f, 0.0f, -1.0f);
		DirectionalLight light1 = new DirectionalLight(light1Color, light1Direction);
		light1.setInfluencingBounds(bounds);
		light1.setCapability(Node.ALLOW_BOUNDS_READ);

		lightGroup.addChild(light1);

		Vector3f light2Direction = new Vector3f(1.0f, 0.0f, 1.0f);
		DirectionalLight light2 = new DirectionalLight(light1Color, light2Direction);
		light2.setInfluencingBounds(bounds);
		light2.setCapability(Node.ALLOW_BOUNDS_READ);

		lightGroup.addChild(light2);

		Color3f ambientLightColor = new Color3f(0.2f, 0.2f, 0.2f);
		AmbientLight ambientLight = new AmbientLight(ambientLightColor);
		ambientLight.setInfluencingBounds(bounds);
		ambientLight.setCapability(Node.ALLOW_BOUNDS_READ);

		lightGroup.addChild(ambientLight);


		root.addChild(lightGroup);
	}

	private BranchGroup createAxis(BoundingSphere bsphere)
	{
		float bsRadius = (float) bsphere.getRadius();
		Point3d bsCenter = new Point3d();
		bsphere.getCenter(bsCenter);

		BranchGroup axisBG = new BranchGroup();
		TransformGroup axis = new TransformGroup();
		Transform3D transform = new Transform3D();
		axis.setTransform(transform);

		//		Appearance sphereApp = new Appearance();
		//		PolygonAttributes patt = new PolygonAttributes();
		//		patt.setPolygonMode(PolygonAttributes.POLYGON_LINE);
		//		sphereApp.setPolygonAttributes(patt);
		//		Sphere sphere = new Sphere(bsRadius, sphereApp);
		//		axisBG.addChild(sphere);

		Color3f red = new Color3f(1, 0, 0);
		Color3f green = new Color3f(0, 1, 0);
		Color3f blue = new Color3f(0, 0, 1);

		Appearance appRed = new Appearance();
		ColoringAttributes ca = new ColoringAttributes(red, ColoringAttributes.SHADE_FLAT);
		appRed.setColoringAttributes(ca);

		Point3f[] xAxisPts = new Point3f[2];
		xAxisPts[0] = new Point3f((float)bsCenter.x, (float)bsCenter.y, (float)bsCenter.z);
		xAxisPts[1] = new Point3f((float)bsCenter.x + bsRadius, (float)bsCenter.y, (float)bsCenter.z);
		LineArray xAxisLine = new LineArray(2, LineArray.COORDINATES);
		xAxisLine.setCoordinates(0, xAxisPts);
		Shape3D xAxis = new Shape3D(xAxisLine, appRed);
		xAxis.setUserData("X Axis");
		axis.addChild(xAxis);

		Appearance appGreen = new Appearance();
		ColoringAttributes cag = new ColoringAttributes(green,
				ColoringAttributes.SHADE_FLAT);
		appGreen.setColoringAttributes(cag);

		Point3f[] yAxisPts = new Point3f[2];
		yAxisPts[0] = new Point3f((float)bsCenter.x, (float)bsCenter.y, (float)bsCenter.z);
		yAxisPts[1] = new Point3f((float)bsCenter.x, (float)bsCenter.y+ bsRadius, (float)bsCenter.z);
		LineArray yAxisLine = new LineArray(2, LineArray.COORDINATES);
		yAxisLine.setCoordinates(0, yAxisPts);
		Shape3D yAxis = new Shape3D(yAxisLine, appGreen);
		yAxis.setUserData("Y Axis");
		axis.addChild(yAxis);

		Appearance appBlue = new Appearance();
		ColoringAttributes cab = new ColoringAttributes(blue,
				ColoringAttributes.SHADE_FLAT);
		appBlue.setColoringAttributes(cab);

		Point3f[] zAxisPts = new Point3f[2];
		zAxisPts[0] = new Point3f((float)bsCenter.x, (float)bsCenter.y, (float)bsCenter.z);
		zAxisPts[1] = new Point3f((float)bsCenter.x, (float)bsCenter.y, (float)bsCenter.z+ bsRadius);
		LineArray zAxisLine = new LineArray(2, LineArray.COORDINATES);
		zAxisLine.setCoordinates(0, zAxisPts);
		Shape3D zAxis = new Shape3D(zAxisLine, appBlue);
		zAxis.setUserData("Z Axis");
		axis.addChild(zAxis);

		axisBG.addChild(axis);

		return axisBG;
	}

	


	private BranchGroup createBoundingBox(double[] minBounding, double[] maxBounding)
	{
		Appearance appearance = new Appearance();
		PolygonAttributes pAtt = new PolygonAttributes();
		pAtt.setPolygonMode(PolygonAttributes.POLYGON_LINE);
		ColoringAttributes colAtt = new ColoringAttributes();
		colAtt.setColor(new Color3f(Color.WHITE));
		appearance.setPolygonAttributes(pAtt);
		appearance.setColoringAttributes(colAtt);

		double width = (maxBounding[0] - minBounding[0])/2;
		double height = (maxBounding[1] - minBounding[1])/2;
		double depth = (maxBounding[2] - minBounding[2])/2;

		Box box = new Box((float)width, (float)height, (float)depth, Box.GEOMETRY_NOT_SHARED | Box.GENERATE_NORMALS, appearance);

		BranchGroup boxBg = new BranchGroup();
		TransformGroup boxTg = new TransformGroup();
		Transform3D boxt3d = new Transform3D();
		boxt3d.setTranslation(new Vector3d(minBounding[0] + width, minBounding[1] + height, minBounding[2] + depth));
		boxTg.setTransform(boxt3d);
		boxTg.addChild(box);

		boxBg.addChild(boxTg);

		return boxBg;
	}

	private BranchGroup createPoint(double[] point, Color color)
	{
		Appearance appearance = new Appearance();
		ColoringAttributes colAtt = new ColoringAttributes();
		colAtt.setColor(new Color3f(color));
		PointAttributes pAtt = new PointAttributes();
		pAtt.setPointSize(3);
		appearance.setColoringAttributes(colAtt);
		appearance.setPointAttributes(pAtt);

		PointArray pArray = new PointArray(1, PointArray.COORDINATES);
		pArray.setCoordinate(0, new Point3d(point));
		Shape3D pS3d = new Shape3D(pArray, appearance);

		BranchGroup bg = new BranchGroup();
		bg.addChild(pS3d);
		return bg;
	}
}


